03.2 精通自定义 View 之属性动画——自定义插值器与 Evaluator

返回自定义 View 目录

视图动画,只能设置插值器;但对于 Animator 而言,不仅可以设置插值器,还可以设置 Evaluator。

3.2.1 自定义插值器

插值器就是用来控制动画的区间值如何被计算出来的。

1、系统自带插值器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class AccelerateDecelerateInterpolator extends BaseInterpolator {
public AccelerateDecelerateInterpolator() {
}
@SuppressWarnings({"UnusedDeclaration"})
public AccelerateDecelerateInterpolator(Context context, AttributeSet attrs) {
}
public float getInterpolation(float input) {
return (float)(Math.cos((input + 1) * Math.PI) / 2.0f) + 0.5f;
}
}
...
// 最终实现的接口:TimeInterpolator
public interface TimeInterpolator {
float getInterpolation(float input);
}

在 TimeInterpolator 的代码里,它只有一个函数 float getInterpolation(float input)。

  • 参数 input:input 是 Float 类型的,它的取值范围是 0 ~ 1,表示当前动画的进度。取 0 时表示动画刚开始,取 1 时表示动画结束,取 0.5 时表示动画中间的位置,其他以此类推。它是时间概念。
  • 返回值:表示当前实际想要显示的进度。取值可以超过 ,也可以小于 0。超过 1 表示已经超过目标值,小于 0 表示小于开始位置。

而 AccelerateDecelerateInterpolator 返回的显示进度值为:

1
return (float)(Math.cos((input + 1) * Math.PI) / 2.0f) + 0.5f;

input 是从 0 到 1,即cos(π) 到 cos(2π) 的结果值是从 -1 到 1。最终返回结果还是从 0 到 1,但是由于 Math.cos() 导致速率变化了,先加速后减速。

2、自定义插值器示例

想要先减速后加速的效果,可以重写 getInterpolation 方法。

1
2
3
float x = (-1 + input * 2.0f) / 4f; // -0.25 ~ 0.25
float y = (float) Math.tan(x * Math.PI);
return y / 2f + 0.5f;

由于 tan 函数曲线变化不明显,导致加速减速效果也不明显,趋向于匀速。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class DecelerateAccelerateInterpolator implements TimeInterpolator {
public DecelerateAccelerateInterpolator() {
}
@SuppressWarnings({"UnusedDeclaration"})
public DecelerateAccelerateInterpolator(Context context, AttributeSet attrs) {
}
public float getInterpolation(float input) {
float x = (-1 + input * 2.0f) / 4f; // -0.25 ~ 0.25
float y = (float) Math.tan(x * Math.PI);
return y / 2f + 0.5f;
}
}

使用

1
animator.setInterpolator(new DecelerateAccelerateInterpolator());

3.2.2 Evaluator

1. 概述

上图所述的 4 个步骤的具体含义是:
1)ofInt(0,400):表示指定动画的数值区间,从 0 运动到 400。
2)插值器:在动画开始后通过插值器会返回当前动画进度所对应的数值进度,这个数值进度是以小数表示的,如 0.2。
3)Evaluator:我们通过监听器拿到的是当前动画所对应的具体数值,而不是用小数表示的数值。那么必须有一个地方会根据当前的数值进度将其转换为对应的数值,这个地方就是 Evaluator。Evaluator 用于将从插值器返回的数值进度(小数,0 - 1.0)转换成对应的数值。
4)监听器返回:在 AnimatorUpdateListener 监听器中使用 animation.getAnimatedValue() 函数拿到 Evaluator 中返回的数值。

2. 各种 Evaluator

插值器返回的小数值表示的是当前动画的数值进度,这对于无论是使用 ofFloat() 函数还是使用 ofInt() 函数定义的动画都是适用的。因为无论是什么动画,它的进度必然在 0~1 之间。0 表示还没开始,1 表示动画结束,这对于任何动画都是适用的。

而 Evaluator 则不一样,它把插值器返回的小数进度转换成当前数值进度所对应的值。如果使用 ofInt() 函数来定义动画,动画中的值应该都是 Integer 类型的,所对应的 Evaluator 在返回值时,必然返回 Integer 类型的值;如果使用 ofFloat() 函数来定义动画,动画中的值都是 Float 类型的,Evaluator 在返回值时,必然返回 Float 类型的值。

所以,每种定义方式所对应的 Evaluator 必然是它专用的。Evaluator 专用的原因在于动画数值类型不一样,在通过 Evaluator 返回时会报强转错误,所以只有在动画数值类型一样时,所对应的 Evaluator 才能通用。ofInt() 函数对应的 Evaluator 类名为 IntEvaluator,而 ofFloat() 函数对应的 Evaluator 类名为 FloatEvaluator。

通过 animator.setEvaluator() 函数来设置 Evaluator,比如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
private void startAnimationArgbEvaluator() {
ValueAnimator animator = ValueAnimator.ofInt(0xffffff00, 0xff0000ff);
// 设置 Evaluator
animator.setEvaluator(new ArgbEvaluator());
animator.setDuration(3000);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
int curValue = (Integer) animation.getAnimatedValue();
tv_text.setBackgroundColor(curValue);
}
});
animator.start();
}

ofInt() 函数的默认 Evaluator 是 IntEvaluator,而 ofFloat() 函数的默认 Evaluator 则是 FloatEvaluator。

可以继续看下 IntEvaluator 的源码:

1
2
3
4
5
6
7
public class IntEvaluator implements TypeEvaluator<Integer> {
public Integer evaluate(float fraction, Integer startValue, Integer endValue) {
int startInt = startValue;
// 当前的值 = 100 + (400 - 100) * 显示进度
return (int)(startInt + fraction * (endValue - startInt));
}
}

在 IntEvaluator 中只有一个函数 evaluate(float fraction, Integer startValue, Integer endValue):

  • fraction 参数就是插值器中的返回值,表示当前动画的数值进度,以百分制的小数表示。
  • startValue 和 endValue 分别对应 ofInt(int start,int end) 函数中 start 和 end 的数值。假设当我们定义的动画 ofInt(100,400) 进行到数值进度 20% 的时候,那么此时在 evaluate() 函数中,fraction 的值就是 0.2,startValue 的值是 100,endValue 的值是 400。
  • 返回值就是当前数值进度所对应的具体数值,这个数值就是我们在 AnimatorUpdateListener 监听器中通过 animation.getAnimatedValue() 函数得到的数值。

总之:既可以通过重写插值器改变数值进度来改变数值位置,也可以通过改变 Evaluator 中数值进度所对应的具体数值来改变数值位置。

3. 简单实现 Evaluator

自定义 MyEvaluator,并设定一个位移量。

1
2
3
4
5
6
7
8
9
10
11
public class MyEvaluator implements TypeEvaluator<Integer> {
private int offset;
public MyEvaluator(int offset) {
this.offset = offset;
}
@Override
public Integer evaluate(float fraction, Integer startValue, Integer endValue) {
int startInt = startValue;
return (int) (startInt + (endValue - startInt) * fraction + offset);
}
}

使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
mView = findViewById(R.id.tv);
ValueAnimator animator = ValueAnimator.ofInt(0, 400);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
int curValue = (Integer) animation.getAnimatedValue();
mView.layout(curValue, curValue,
curValue + mView.getWidth(),
curValue + mView.getHeight());
}
});
animator.setDuration(1000);
animator.setEvaluator(new MyEvaluator(200));
animator.start();

4. 自定义 Evaluator 实现倒序输出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class ReverseEvaluator implements TypeEvaluator<Integer> {
private boolean isReverse = true;
public MyEvaluator() {
}
public MyEvaluator(boolean isReverse) {
this.isReverse = isReverse;
}
@Override
public Integer evaluate(float fraction, Integer startValue, Integer endValue) {
int startInt = startValue;
if (isReverse) {
return (int) (endValue - (endValue - startInt) * fraction);
} else {
return (int) (startInt + (endValue - startInt) * fraction);
}
}
}

5. ArgbEvaluator

除 IntEvaluator 和 FloatEvaluator 外,在 android.animation 包下还有另一个 Evaluator,名为 ArgbEvaluator,它是用来实现颜色值过渡转换的。

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
public void startAnim(View v) {
ValueAnimator animator = ValueAnimator.ofInt(0xFFFFFF00, 0xFF0000FF);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
int curValue = (Integer) animation.getAnimatedValue();
mView.setBackgroundColor(curValue);
}
});
animator.setDuration(3000);
animator.setEvaluator(new ArgbEvaluator());
animator.start();
}

实现原理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
public class ArgbEvaluator implements TypeEvaluator {
public Object evaluate(float fraction, Object startValue, Object endValue) {
int startInt = (Integer) startValue;
float startA = ((startInt >> 24) & 0xff) / 255.0f;
float startR = ((startInt >> 16) & 0xff) / 255.0f;
float startG = ((startInt >> 8) & 0xff) / 255.0f;
float startB = ( startInt & 0xff) / 255.0f;
int endInt = (Integer) endValue;
float endA = ((endInt >> 24) & 0xff) / 255.0f;
float endR = ((endInt >> 16) & 0xff) / 255.0f;
float endG = ((endInt >> 8) & 0xff) / 255.0f;
float endB = ( endInt & 0xff) / 255.0f;
// convert from sRGB to linear
startR = (float) Math.pow(startR, 2.2);
startG = (float) Math.pow(startG, 2.2);
startB = (float) Math.pow(startB, 2.2);
endR = (float) Math.pow(endR, 2.2);
endG = (float) Math.pow(endG, 2.2);
endB = (float) Math.pow(endB, 2.2);
// compute the interpolated color in linear space
float a = startA + fraction * (endA - startA);
float r = startR + fraction * (endR - startR);
float g = startG + fraction * (endG - startG);
float b = startB + fraction * (endB - startB);
// convert back to sRGB in the [0..255] range
a = a * 255.0f;
r = (float) Math.pow(r, 1.0 / 2.2) * 255.0f;
g = (float) Math.pow(g, 1.0 / 2.2) * 255.0f;
b = (float) Math.pow(b, 1.0 / 2.2) * 255.0f;
return Math.round(a) << 24 | Math.round(r) << 16
| Math.round(g) << 8 | Math.round(b);
}
}